1
2
3
4
5
6 package ch.twiddlefinger.inet.rewinder.model.parser.conversion;
7
8 import java.util.Collection;
9 import java.util.Collections;
10 import java.util.Iterator;
11 import java.util.Map;
12 import java.util.Set;
13
14
15 /***
16 * <p> This class provides cache access to <code>Map</code> collections.</p>
17 *
18 * <p> Instance of this class can be used as "proxy" for any collection
19 * implementing the <code>java.util.Map</code> interface.</p>
20 *
21 * <p> Typically, {@link CachedMap} are used to accelerate access to
22 * large collections when the access to the collection is not evenly
23 * distributed (associative cache). The performance gain is about
24 * 50% for the fastest hash map collections (e.g. {@link FastMap}).
25 * For slower collections such as <code>java.util.TreeMap</code>,
26 * non-resizable {@link FastMap} (real-time) or database access,
27 * performance can be of several orders of magnitude.</p>
28 *
29 * <p> <b>Note:</b> The keys used to access elements of a {@link CachedMap} do
30 * not need to be immutable as they are not stored in the cache
31 * (only keys specified by the {@link #put} method are).
32 * In other words, access can be performed using mutable keys as long
33 * as these keys can be compared for equality with the real map's keys
34 * (e.g. same <code>hashCode</code> values).</p>
35 *
36 * <p> This implementation is not synchronized. Multiple threads accessing
37 * or modifying the collection must be synchronized externally.</p>
38 *
39 * <p><i> This class is <b>public domain</b> (not copyrighted).</i></p>
40 *
41 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
42 * @version 5.3, October 30, 2003
43 */
44 public final class CachedMap implements Map {
45 /***
46 * Holds the FastMap backing this collection
47 * (<code>null</code> if generic backing map).
48 */
49 private final FastMap _backingFastMap;
50
51 /***
52 * Holds the generic map backing this collection.
53 */
54 private final Map _backingMap;
55
56 /***
57 * Holds the keys of the backing map (key-to-key mapping).
58 * (<code>null</code> if FastMap backing map).
59 */
60 private final FastMap _keysMap;
61
62 /***
63 * Holds the cache's mask (capacity - 1).
64 */
65 private final int _mask;
66
67 /***
68 * Holds the keys being cached.
69 */
70 private final Object[] _keys;
71
72 /***
73 * Holds the values being cached.
74 */
75 private final Object[] _values;
76
77 /***
78 * Creates a cached map backed by a {@link FastMap}.
79 * The default cache size and map capacity is set to <code>256</code>
80 * entries.
81 */
82 public CachedMap() {
83 this(256, new FastMap());
84 }
85
86 /***
87 * Creates a cached map backed by a {@link FastMap} and having the
88 * specified cache size.
89 *
90 * @param cacheSize the cache size, the actual cache size is the
91 * first power of 2 greater or equal to <code>cacheSize</code>.
92 * This is also the initial capacity of the backing map.
93 */
94 public CachedMap(int cacheSize) {
95 this(cacheSize, new FastMap(cacheSize));
96 }
97
98 /***
99 * Creates a cached map backed by the specified map and having the specified
100 * cache size. In order to maitain cache veracity, it is critical
101 * that <b>all</b> update to the backing map is accomplished through the
102 * {@link CachedMap} instance; otherwise {@link #flush} has to be called.
103 *
104 * @param cacheSize the cache size, the actual cache size is the
105 * first power of 2 greater or equal to <code>cacheSize</code>.
106 * @param backingMap the backing map to be "wrapped" in a cached map.
107 */
108 public CachedMap(int cacheSize, Map backingMap) {
109
110 int actualCacheSize = 1;
111
112 while (actualCacheSize < cacheSize) {
113 actualCacheSize <<= 1;
114 }
115
116
117 _keys = new Object[actualCacheSize];
118 _values = new Object[actualCacheSize];
119 _mask = actualCacheSize - 1;
120
121
122 if (backingMap instanceof FastMap) {
123 _backingFastMap = (FastMap) backingMap;
124 _backingMap = _backingFastMap;
125 _keysMap = null;
126 } else {
127 _backingFastMap = null;
128 _backingMap = backingMap;
129 _keysMap = new FastMap(backingMap.size());
130
131 for (Iterator i = backingMap.keySet().iterator(); i.hasNext();) {
132 Object key = i.next();
133 _keysMap.put(key, key);
134 }
135 }
136 }
137
138 /***
139 * Returns the actual cache size.
140 *
141 * @return the cache size (power of 2).
142 */
143 public int getCacheSize() {
144 return _keys.length;
145 }
146
147 /***
148 * Returns the backing map. If the backing map is modified directly,
149 * this {@link CachedMap} has to be flushed.
150 *
151 * @return the backing map.
152 * @see #flush
153 */
154 public Map getBackingMap() {
155 return (_backingFastMap != null) ? _backingFastMap : _backingMap;
156 }
157
158 /***
159 * Flushes the key/value pairs being cached. This method should be called
160 * if the backing map is externally modified.
161 */
162 public void flush() {
163 for (int i = 0; i < _keys.length; i++) {
164 _keys[i] = null;
165 _values[i] = null;
166 }
167
168 if (_keysMap != null) {
169
170 for (Iterator i = _backingMap.keySet().iterator(); i.hasNext();) {
171 Object key = i.next();
172 _keysMap.put(key, key);
173 }
174 }
175 }
176
177 /***
178 * Returns the value to which this map maps the specified key.
179 * First, the cache is being checked, then if the cache does not contains
180 * the specified key, the backing map is accessed and the key/value
181 * pair is stored in the cache.
182 *
183 * @param key the key whose associated value is to be returned.
184 * @return the value to which this map maps the specified key, or
185 * <code>null</code> if the map contains no mapping for this key.
186 * @throws ClassCastException if the key is of an inappropriate type for
187 * the backing map (optional).
188 * @throws NullPointerException if the key is <code>null</code>.
189 */
190 public Object get(Object key) {
191 int index = key.hashCode() & _mask;
192
193 return key.equals(_keys[index]) ? _values[index]
194 : getCacheMissed(key, index);
195 }
196
197 private Object getCacheMissed(Object key, int index) {
198 if (_backingFastMap != null) {
199 Entry entry = _backingFastMap.getEntry(key);
200
201 if (entry != null) {
202 _keys[index] = entry.getKey();
203
204 Object value = entry.getValue();
205 _values[index] = value;
206
207 return value;
208 } else {
209 return null;
210 }
211 } else {
212
213 Object mapKey = _keysMap.get(key);
214
215 if (mapKey != null) {
216 _keys[index] = mapKey;
217
218 Object value = _backingMap.get(key);
219 _values[index] = value;
220
221 return value;
222 } else {
223 return null;
224 }
225 }
226 }
227
228 /***
229 * Associates the specified value with the specified key in this map.
230 *
231 * @param key the key with which the specified value is to be associated.
232 * @param value the value to be associated with the specified key.
233 * @return the previous value associated with specified key, or
234 * <code>null</code> if there was no mapping for the key.
235 * @throws UnsupportedOperationException if the <code>put</code> operation
236 * is not supported by the backing map.
237 * @throws ClassCastException if the class of the specified key or value
238 * prevents it from being stored in this map.
239 * @throws IllegalArgumentException if some aspect of this key or value
240 * prevents it from being stored in this map.
241 * @throws NullPointerException if the key is <code>null</code>.
242 */
243 public Object put(Object key, Object value) {
244
245 int index = key.hashCode() & _mask;
246
247 if (key.equals(_keys[index])) {
248 _values[index] = value;
249 } else if (_keysMap != null) {
250 _keysMap.put(key, key);
251 }
252
253
254 return _backingMap.put(key, value);
255 }
256
257 /***
258 * Removes the mapping for this key from this map if it is present.
259 *
260 * @param key key whose mapping is to be removed from the map.
261 * @return previous value associated with specified key,
262 * or <code>null</code> if there was no mapping for key.
263 * @throws ClassCastException if the key is of an inappropriate type for
264 * the backing map (optional).
265 * @throws NullPointerException if the key is <code>null</code>.
266 * @throws UnsupportedOperationException if the <code>remove</code> method
267 * is not supported by the backing map.
268 */
269 public Object remove(Object key) {
270
271 int index = key.hashCode() & _mask;
272
273 if (key.equals(_keys[index])) {
274 _keys[index] = null;
275 }
276
277
278 if (_keysMap != null) {
279 _keysMap.remove(key);
280 }
281
282
283 return _backingMap.remove(key);
284 }
285
286 /***
287 * Indicates if this map contains a mapping for the specified key.
288 *
289 * @param key the key whose presence in this map is to be tested.
290 * @return <code>true</code> if this map contains a mapping for the
291 * specified key; <code>false</code> otherwise.
292 */
293 public boolean containsKey(Object key) {
294
295 int index = key.hashCode() & _mask;
296
297 if (key.equals(_keys[index])) {
298 return true;
299 } else {
300
301 return _backingMap.containsKey(key);
302 }
303 }
304
305 /***
306 * Returns the number of key-value mappings in this map. If the
307 * map contains more than <code>Integer.MAX_VALUE</code> elements,
308 * returns <code>Integer.MAX_VALUE</code>.
309 *
310 * @return the number of key-value mappings in this map.
311 */
312 public int size() {
313 return _backingMap.size();
314 }
315
316 /***
317 * Returns <code>true</code> if this map contains no key-value mappings.
318 *
319 * @return <code>true</code> if this map contains no key-value mappings.
320 */
321 public boolean isEmpty() {
322 return _backingMap.isEmpty();
323 }
324
325 /***
326 * Returns <code>true</code> if this map maps one or more keys to the
327 * specified value.
328 *
329 * @param value value whose presence in this map is to be tested.
330 * @return <code>true</code> if this map maps one or more keys to the
331 * specified value.
332 * @throws ClassCastException if the value is of an inappropriate type for
333 * the backing map (optional).
334 * @throws NullPointerException if the value is <code>null</code> and the
335 * backing map does not not permit <code>null</code> values.
336 */
337 public boolean containsValue(Object value) {
338 return _backingMap.containsValue(value);
339 }
340
341 /***
342 * Copies all of the mappings from the specified map to this map
343 * (optional operation). This method automatically flushes the cache.
344 *
345 * @param map the mappings to be stored in this map.
346 * @throws UnsupportedOperationException if the <code>putAll</code> method
347 * is not supported by the backing map.
348 * @throws ClassCastException if the class of a key or value in the
349 * specified map prevents it from being stored in this map.
350 * @throws IllegalArgumentException some aspect of a key or value in the
351 * specified map prevents it from being stored in this map.
352 * @throws NullPointerException the specified map is <code>null</code>, or
353 * if the backing map does not permit <code>null</code> keys or
354 * values, and the specified map contains <code>null</code> keys or
355 * values.
356 */
357 public void putAll(Map map) {
358 _backingMap.putAll(map);
359 flush();
360 }
361
362 /***
363 * Removes all mappings from this map (optional operation). This method
364 * automatically flushes the cache.
365 *
366 * @throws UnsupportedOperationException if clear is not supported by the
367 * backing map.
368 */
369 public void clear() {
370 _backingMap.clear();
371 flush();
372 }
373
374 /***
375 * Returns an <b>unmodifiable</b> view of the keys contained in this
376 * map.
377 *
378 * @return an unmodifiable view of the keys contained in this map.
379 */
380 public Set keySet() {
381 return Collections.unmodifiableSet(_backingMap.keySet());
382 }
383
384 /***
385 * Returns an <b>unmodifiable</b> view of the values contained in this map.
386 *
387 * @return an unmodifiable view of the values contained in this map.
388 */
389 public Collection values() {
390 return Collections.unmodifiableCollection(_backingMap.values());
391 }
392
393 /***
394 * Returns an <b>unmodifiable</b> view of the mappings contained in this
395 * map. Each element in the returned set is a <code>Map.Entry</code>.
396 *
397 * @return an unmodifiable view of the mappings contained in this map.
398 */
399 public Set entrySet() {
400 return Collections.unmodifiableSet(_backingMap.entrySet());
401 }
402
403 /***
404 * Compares the specified object with this map for equality. Returns
405 * <tt>true</tt> if the given object is also a map and the two Maps
406 * represent the same mappings.
407 *
408 * @param o object to be compared for equality with this map.
409 * @return <code>true</code> if the specified object is equal to this map.
410 */
411 public boolean equals(Object o) {
412 return _backingMap.equals(o);
413 }
414
415 /***
416 * Returns the hash code value for this map.
417 *
418 * @return the hash code value for this map.
419 */
420 public int hashCode() {
421 return _backingMap.hashCode();
422 }
423 }